﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using SimulationPresenter.Presentation;
using SimulationPresenter.Simulation.Primitive;
using SimulationPresenter.Theater.Actors;
using SimulationPresenter.Theater.Joints;
using Box=SimulationPresenter.Theater.Actors.BoxActor;
using Enviroment = SimulationPresenter.Simulation.SimulationEnvironment;
using SimulationPresenter.Simulation.Helpers.BaseShapes;
using SimulationPresenter.Presentation.Helpers;
using SimulationPresenter.Presentation.Primitives;
using SimulationPresenter.Simulation.Enums;

namespace SimulationPresenter.Theater
{
    internal delegate void ActorAdded(Actor actor);
    internal delegate void ActorRemoved(Actor actor);

    internal class Scene
    {
        private Enviroment simulationEnviroment;
        private PrimitivesFactory primitivesFactory;
        private DateTime lastSceneUpdate = DateTime.Now;
        private ActorAdded actorAddedAction;
        private ActorRemoved actorRemovedAction;
        
        /// <summary>
        /// Simulation enviroment
        /// </summary>
        private Enviroment SimulationEnviroment
        {
            get
            {
                if (null == simulationEnviroment)
                {
                    simulationEnviroment = new Enviroment();
                }
                return simulationEnviroment;
            }
        }

        /// <summary>
        /// Presentaion elements' factory
        /// </summary>
        private PrimitivesFactory PrimitivesFactory
        {
            get
            {
                if (null == primitivesFactory)
                {
                    primitivesFactory = new PrimitivesFactory();
                }
                return primitivesFactory;
            }
        }

        private List<ISimulatable> Actors = new List<ISimulatable>();
        private List<IJoint> Joints = new List<IJoint>();
        private List<string> ActorsNames = new List<string>();

        /// <summary>
        /// Sets up base enviroment. 
        /// 1. Add ground plane
        /// </summary>
        internal Scene Setup()
        {
            SimulationEnviroment.Core.AddGroundPlane();
            return this;
        }

        /// <summary>
        /// Adds Actor to the Scene
        /// </summary>
        /// <param name="actorPrimitive"></param>
        private void AddActor(ISimulatable actorPrimitive)
        {
            Actors.Add(actorPrimitive);
        }

        /// <summary>
        /// Adds Actor to the Scene
        /// </summary>
        /// <param name="actorPrimitive"></param>
        private void AddJoint(IJoint joint)
        {
            Joints.Add(joint);
        }

        /// <summary>
        /// Updates state of all actors on the Scene
        /// </summary>
        internal void UpdateScene()
        {
            var actorsState = SimulationEnviroment.Core.GetActorsState(DateTime.Now - lastSceneUpdate);
            lastSceneUpdate = DateTime.Now;

            foreach (var actor in Actors)
            {
                actor.UpdateState(actorsState);
            }

        }

        /// <summary>
        /// Draws whole scene into given device
        /// </summary>
        /// <param name="device"></param>
        internal void DrawScene(Device device)
        {
            var actors = Actors.OfType<IDrawable>();
            foreach (var actor in actors)
            {
                actor.DrawActor(device);
            }
        }

        /// <summary>
        /// Creates box with default size, postion and velocity
        /// </summary>
        internal void CreateAndAddDefaultBox()
        {
            CreateAndAddDefaultBox(defaultPosition);
        }

        /// <summary>
        /// Creates box with default size and velocity. Position must be specified
        /// </summary>
        internal void CreateAndAddDefaultBox(Vector3 position)
        {
            var presentation = PrimitivesFactory.CreateBox(defaultSize, position);
            var boxName = GenerateActorName();
            var simulation = new RidigBox()
            {
                Name = boxName,
                Density = defaultElementDensity,
                SizeScalar = defaultSize
            };

            var actor = new Box(presentation, simulation);

            AddActor(actor);

            simulation.Transformation = Matrix.Translation(position);
            simulation.PutOrUpdate(SimulationEnviroment);

            actorAddedAction(actor);
        }

        internal void CreateAndAddCloth()
        {
            CreateAndAddCloth(defaultClothPosition);
        }

        internal void CreateAndAddMesh(string fileName, Device device)
        {
            CreateAndAddMesh(fileName, defaultClothPosition, device);
        }

        private void CreateAndAddMesh(string fileName, Vector3 position, Device device)
        {
            var meshName = GenerateActorName();
            var actors = RidigMeshActor.FromFile(fileName, meshName, defaultPosition, SimulationEnviroment, device);

            foreach (var actor in actors)
            {
                AddActor(actor);
                actorAddedAction(actor);
            }
        }

        internal void CreatePoltergeistFromFile(string fileName, Device device)
        {
            var shapes = MeshShape.FromFile(fileName, device);

            foreach (var shape in shapes)
            {
                var actor = new PoltergeistActor(shape, GenerateActorName());
                actor.ToPosition(defaultPosition);
                AddActor(actor);
                actorAddedAction(actor);
            }
        }

        /// <summary>
        /// Creates box with default size and velocity. Position must be specified
        /// </summary>
        internal void CreateAndAddCloth(Vector3 position)
        {
            //var clothName = GenerateActorName();
            

            //var simulation = new MetalCloth()
            //{
            //    Name = clothName,
            //    ImpulseThreshold = defaultImpulseThreshold,
            //    PenetrationDepth = defaultPenetrationDepth,
            //    Transformation = Matrix.Translation(position)
            //}.SimpleGrid(defaultGridHeight, defaultGridWidth);

            //var actor = new MetalClothActor(simulation);

            //AddActor(actor);

            //simulation.Transformation = Matrix.Translation(position);
            //simulation.PutOrUpdate(SimulationEnviroment);

            //actorAddedAction(actor);
        }

        internal void JoinLastTwoActors()
        {
            var actors = Actors.OfType<IActorPropertiesProvider>();

            if (actors.Count() < 2)
            {
                throw new InvalidOperationException("There is not enough Actors for creating a join");
            }

            var selectedActors = actors.OrderByDescending(x => x.Name).Take(2).ToList();

            JoinActors(selectedActors[0], selectedActors[1]);
        }

        internal void JoinActors(IActorPropertiesProvider actor1, IActorPropertiesProvider actor2)
        {
            var join = new SphericalJoint()
            {
                Actor1 = actor1,
                Actor2 = actor2
            };

            join.CreateJoin(SimulationEnviroment);

            AddJoint(join);
        }

        internal void SetActorToSimulationType(ISimulationTypeChangeableActor actor, SimulationType simulationType)
        {
            if (null == actor)
            {
                throw new ArgumentNullException("actor", "Actor selected to SetActorToSimulationType was null");
            }
            switch (simulationType)
            {
                case SimulationType.Dummy:
                    actor.As<DummySimulative>(simulationEnviroment);
                    break;
                case SimulationType.Ridig:
                    actor.As<RidigMesh>(simulationEnviroment);
                    break;
                case SimulationType.MetalCloth:
                    actor.As<MetalCloth>(simulationEnviroment);
                    break;
                case SimulationType.SoftBody:
                    throw new NotImplementedException("SoftBody is not implemented as an option of simulation type change");
                default:
                    throw new NotImplementedException("This simulation type is not implemented as an option of simulation type change");
            }
            actor.CommitToScene(simulationEnviroment);
        }

        internal void BindAddActorAction(ActorAdded action)
        {
            actorAddedAction += action;
        }

        internal void BindRemoveActorAction(ActorRemoved action)
        {
            actorRemovedAction += action;
        }

        /// <summary>
        /// Generates name for next actor 
        /// </summary>
        /// <returns></returns>
        private string GenerateActorName()
        {
            string name;
            int collectionNumber = 0;
            do
            {
                //To avoid infinite looping
                if (collectionNumber > collectionSafeAmount)
                {
                    throw new InvalidOperationException(
                        "Generated collection number indicates kind of infinite loop. Abort!");
                }
                //Name format is: <ActorType><Indexer>-<CollectionIndexer> (example: ACTOR001-001)
                name = string.Format("{0}{1}-{2}", 
                                    defaultActorNamePrefix, 
                                    Actors.Count().ToString("000"),
                                    collectionNumber.ToString("000"));
                collectionNumber++;
            } while (ActorsNames.Contains(name));

            ActorsNames.Add(name);

            return name;
        }

        private readonly float defaultSize = 2;
        private readonly Vector3 defaultPosition = new Vector3(0, 20, 0);
        private readonly Vector3 defaultClothPosition = new Vector3(-5, 20, -5);
        private readonly string defaultActorNamePrefix = "ACTOR";
        private readonly int collectionSafeAmount = 100;
        private readonly float defaultElementDensity = 1.0f;
        private readonly float defaultImpulseThreshold = 1.0f;
        private readonly float defaultPenetrationDepth = 5.0f;
        private readonly int defaultGridHeight = 10;
        private readonly int defaultGridWidth = 10;

    }
}
